package br.com.puertorico.model.querybuilder.utils;

import java.util.Stack;
import java.util.StringTokenizer;

/**
 *
 * @author daniel
 * @version 1.0.0
 */
public class PostfixExpressionBuilder {

	/**
	 * @param infix
	 * @return expressao posfixada
	 */
	public static String infixToPostfix(String infix) {
		infix = prepare(infix);
		StringTokenizer st = new StringTokenizer(infix);
		StringBuilder postfix = new StringBuilder();
		Stack<String> stack = new Stack<String>();
		while (st.hasMoreTokens()) {
			String token = st.nextToken();
			switch (getTokenType(token)) {
			case OPERAND:
				postfix.append(" ").append(token);
				break;
			case OPERATOR:
				dealWithOperator(token, postfix, stack);
				break;
			case OPENING_BRACKET:
				stack.push(token);
				break;
			case CLOSING_BRACKET:
				dealWithClosingBracket(postfix, stack);
				break;
			}
		}
		while (!stack.isEmpty()) {
			postfix.append(" ").append(stack.pop());
		}

		return postfix.toString().trim();
	}

	/**
	 * @param postfix
	 * @param stack
	 */
	static void dealWithClosingBracket(StringBuilder postfix,
			Stack<String> stack) {
		String token = (!stack.isEmpty()) ? stack.pop() : null;
		while (token != null
				&& getTokenType(token) != TokenTipe.OPENING_BRACKET) {
			postfix.append(" ").append(token);
			token = (!stack.isEmpty()) ? stack.pop() : null;
		}
	}

	/**
	 * @param operador
	 * @param postfix
	 * @param stack
	 */
	static void dealWithOperator(String operador, StringBuilder postfix,
			Stack<String> stack) {
		String token = (!stack.isEmpty()) ? stack.peek() : null;
		if (operador.equals("OR")) {
			/*
			 * o While there is an operator B of higher or equal precidence than
			 * A at the top of the stack, pop B off the stack and append it to
			 * the output.
			 */
			while (token != null && getTokenType(token) == TokenTipe.OPERATOR) {
				postfix.append(" ").append(stack.pop());
				token = (!stack.isEmpty()) ? stack.peek() : null;
			}
		} else {
			/*
			 * o While there is an operator B of higher or equal precidence than
			 * A at the top of the stack, pop B off the stack and append it to
			 * the output.
			 */
			while (token != null && token.equals("AND")) {
				postfix.append(" ").append(stack.pop());
				token = (!stack.isEmpty()) ? stack.peek() : null;
			}
		}
		stack.push(operador);
	}

	static TokenTipe getTokenType(String token) {
		if (token.equals("OR") || token.equals("AND")) {
			return TokenTipe.OPERATOR;
		}
		if (!token.equals("OR") && !token.equals("AND") && !token.equals(")")
				&& !token.equals("(")) {
			return TokenTipe.OPERAND;
		}
		if (token.equals(")")) {
			return TokenTipe.CLOSING_BRACKET;
		}
		if (token.equals("(")) {
			return TokenTipe.OPENING_BRACKET;
		}
		throw new IllegalArgumentException();
	}

	/**
	 * @param infix
	 * @return
	 */
	static String prepare(String infix) {
		infix = infix.trim().replaceAll("\\s+", " ");
		infix = infix.replaceAll("\\(\\s", "(");
		infix = infix.replaceAll("\\s\\)", ")");
		infix = infix
				.trim()
				.replaceAll(
						"(\\)(A|a)(N|n)(D|d)\\()|(\\)\\s(A|a)(N|n)(D|d)\\()|(\\)(A|a)(N|n)(D|d)\\s\\()",
						") AND (");
		infix = infix
				.trim()
				.replaceAll(
						"(\\)(O|o)(R|r)\\()|(\\)\\s(O|o)(R|r)\\()|(\\)(O|o)(R|r)\\s\\()",
						") OR (");
		infix = infix.trim().replaceAll("(\\s(A|a)(N|n)(D|d)\\s)", " AND ");
		infix = infix.trim().replaceAll("(\\s(O|o)(R|r)\\s)", " OR ");
		infix = infix.replaceAll("\\(", "( ");
		infix = infix.replaceAll("\\)", " )");
		return infix;
	}

	enum TokenTipe {
		OPERAND, OPERATOR, OPENING_BRACKET, CLOSING_BRACKET
	}
}
